#include <crsparse.h>
using namespace crsparse;
#include <crhuffmanex.h>
using namespace crhuffman;

struct header_mask
{
	static const byte use_sparse = 0x40;
	static const byte tiny_sizes = 0x20;
	static const byte half_bytes = 0x10;
	static const byte no_huffman = 0x08;
	static const byte bi_of_size = 0x07;
};

inline void rawcompress(istream_base& is, ostream_base& os, byte use_sparse)
{
	uint32 len = (uint32)is.remain();
	if (use_sparse) {
		os.putuint(len);
		os.putuint(len);
		os.putbyte(header_mask::no_huffman | header_mask::use_sparse);
		direct_pusher direct(os);
		sparse_encoder<direct_pusher> sparseencoder;
		sparseencoder.setnext(&direct);
		sparseencoder.input(is.tell(), len);
		sparseencoder.final();
	}
	else {
		os.putuint(len);
		os.putuint(len);
		os.putbyte(header_mask::no_huffman);
		iostransfer(os, is, len);
	}
}

struct return_val
{
	byte usesparse;
	char obi;
	return_val(byte u, char o) : usesparse(u), obi(o) {}
};

inline return_val precompress(istream_base& is, ostream_base& os, code_table_wrap<256>& codes)
{
	enum {
		mini_doc = 1024, // mini document should be compressed by 16 bit-space Huffman
		huge_doc = 256 * 1024 * 1024 // huge document should be divided to sections
	};

	huffman_builder huffmanbuilder0;
	huffman_builder huffmanbuilder1;
	sparse_encoder<huffman_builder> sparsebuilder;
	sparsebuilder.setnext(&huffmanbuilder1);

	try {
		while (true) {
			byte ch = is.getbyte();
			huffmanbuilder0.push(ch);
			sparsebuilder.push(ch);
		}
	}
	catch (exception<stream_base>& e)
	{
		if (e.code == stream_base::eos && e.obj == &is)
		{}
		else CR_Prompt(_T("builder : stream_base raised unknown exception"));
	}

	sparsebuilder.final();
	size_t plain = huffmanbuilder1.total();

	////////////////////////////////////////////////////////////////////////////
	//// for test
	//const bool use_sparse = plain < len;
	const bool use_sparse = true;
	////////////////////////////////////////////////////////////////////////////

	huffman_builder& huffmanbuilder = 
		use_sparse ? huffmanbuilder1 : huffmanbuilder0;

	if (!huffmanbuilder.analyse(plain))
	{
		throw exception<void>(header_mask::no_huffman);
	}

	try {
		if (plain <= mini_doc) {
			frequencies_wrap<16> freqs16;
			freq_merge(freqs16, huffmanbuilder);
			code_table_wrap<16> codes16;
			huffman_tree<16> tree;
			tree.build(freqs16.freqs_);
			tree.generate(codes16.codes_);
			huffman_header<16> header;
			bit_size bsize;
			bool tiny_sizes = header.calcsize(
				codes16.codes_, freqs16.freqs_, bsize);
			byte flag = header_mask::bi_of_size & bsize.obi
				| header_mask::half_bytes;
			if (tiny_sizes)
				flag |= header_mask::tiny_sizes;
			if (use_sparse)
				flag |= header_mask::use_sparse;
			os.putuint(is.remain());
			uint32 enclen = bsize.oBy;
			os.putuint(enclen);
			os.putbyte(flag);
			char obi = header.writecodes(os, codes16.codes_, tiny_sizes);
			code_merge(codes, codes16);
			return return_val(use_sparse, obi);
		}
		else {
			huffman_tree<256> tree;
			tree.build(huffmanbuilder.freqs_);
			tree.generate(codes.codes_);
			huffman_header<256> header;
			bit_size bsize;
			bool tiny_sizes = header.calcsize(
				codes.codes_, huffmanbuilder.freqs_, bsize);
			uint32 enclen = bsize.oBy;
			byte flag = header_mask::bi_of_size & bsize.obi;
			if (tiny_sizes)
				flag |= header_mask::tiny_sizes;
			if (use_sparse)
				flag |= header_mask::use_sparse;
			os.putuint(is.remain());
			os.putuint(enclen);
			os.putbyte(flag);
			char obi = header.writecodes(os, codes.codes_, tiny_sizes);
			return return_val(use_sparse, obi);
		}
	}
	catch (exception<huffman_tree<256>>& e)
	{
		switch (e.code)
		{
		case huffman_tree<256>::load_damaged_stream:
			CR_Prompt(_T("huffman_tree : load_damaged_stream"));break;
		case huffman_tree<256>::grow_damaged_stream:
			CR_Prompt(_T("huffman_tree : grow_damaged_stream"));break;
		case huffman_tree<256>::step_damaged_stream:
			CR_Prompt(_T("huffman_tree : step_damaged_stream"));break;
		default: CR_Prompt(_T("huffman_tree : raised unknown exception"));
		}
	}
	return return_val(use_sparse, 0);
}

inline char shecompress(istream_base& is, ostream_base& os, huffman_encoder& encoder, byte use_sparse)
{
	TRACE(_T("****** encoding bit stream: ******\n"));
	sparse_encoder<huffman_encoder> sparseencoder;

	try {
		if (use_sparse) {
			sparseencoder.setnext(&encoder);
			sparseencoder.input(is.tell(), is.remain());
		}
		else {
			while (true) {
				byte ch = is.getbyte();
				encoder.push(ch);
			}
		}
	}
	catch (exception<stream_base>& e)
	{
		if (e.code == stream_base::eos && e.obj == &is)
		{}
		else CR_Prompt(_T("encoder : stream_base raised unknown exception"));
	}

	sparseencoder.final();

	DEBUGING(encoder.dump(std::ofstream("g:\\text_enc.txt")));
	TRACE(_T("\n"));

	return encoder.final();
}

inline byte* decompress(istream_base& is, ostream_base& os)
{
	const uint32 declen = is.getuint();
	const uint32 enclen = is.getuint();
	const byte flag = is.getbyte();
	const byte use_sparse = flag & header_mask::use_sparse;
	const bit_addr endpos(is.tell() + enclen, flag & header_mask::bi_of_size);

	if (flag & header_mask::no_huffman) {
		TRACE(_T("not compressed with huffman.\n"));
		if (use_sparse) {
			direct_pusher directdecoder(os);

			sparse_decoder<direct_pusher> sparsedecoder;
			sparsedecoder.setnext(&directdecoder);
			sparsedecoder.input(is.tell(), is.remain());
			//ASSERT(os.occupy() == declen);
		}
		else {
			iostransfer(os, is, is.remain());
		}
		return os.tell();
	}
	TRACE(_T("****** load codes: ******\n"));
	char obi;
	code_table_wrap<256> codes;

	try {
		if (flag & header_mask::half_bytes) {
			code_table_wrap<16> codes16;
			huffman_header<16> header;
			obi = header.readcodes(is, codes16.codes_, 
				!!(flag & header_mask::tiny_sizes));
			code_merge(codes, codes16);
		}
		else {
			huffman_header<256> header;
			obi = header.readcodes(is, codes.codes_, 
				!!(flag & header_mask::tiny_sizes));
		}
	}
	catch (exception<stream_base>& e)
	{
		if (e.code == stream_base::eos && e.obj == &is) {}
		else CR_Prompt(_T("encoder : stream_base raised unknown exception"));
	}

	try {
		direct_pusher directdecoder(os);
		if (use_sparse) {
			huffman_decoder<sparse_decoder<direct_pusher>> huffmandecoder(is, 0);
			sparse_decoder<direct_pusher> sparsedecoder;
			huffmandecoder.setnext(&sparsedecoder);
			sparsedecoder.setnext(&directdecoder);
			TRACE(_T("****** restore tree: ******\n"));
			huffmandecoder.restore(codes.codes_);
			TRACE(_T("\n"));
			huffmandecoder.bind(is.tell(), obi, is.remain()*8);
			TRACE(_T("****** decoding bit stream: ******\n"));
			huffmandecoder.bitscan(endpos);
			sparsedecoder.final();
			DEBUGING(huffmandecoder.dump(std::ofstream("g:\\text_dec.txt")));
			TRACE(_T("\n"));
			huffmandecoder.final();
		}
		else {
			huffman_decoder<direct_pusher> huffmandecoder(is, 0);
			huffmandecoder.setnext(&directdecoder);
			TRACE(_T("****** restore tree: ******\n"));
			huffmandecoder.restore(codes.codes_);
			TRACE(_T("\n"));
			huffmandecoder.bind(is.tell(), obi, is.remain()*8);
			TRACE(_T("****** decoding bit stream: ******\n"));
			huffmandecoder.bitscan(endpos);
			DEBUGING(huffmandecoder.dump(std::ofstream("g:\\text_dec.txt")));
			TRACE(_T("\n"));
			huffmandecoder.final();
		}
	}
	catch (exception<huffman_tree<256>>& e)
	{
		switch (e.code)
		{
		case huffman_tree<256>::load_damaged_stream:
			CR_Prompt(_T("huffman_tree : load_damaged_stream"));break;
		case huffman_tree<256>::grow_damaged_stream:
			CR_Prompt(_T("huffman_tree : grow_damaged_stream"));break;
		case huffman_tree<256>::step_damaged_stream:
			CR_Prompt(_T("huffman_tree : step_damaged_stream"));break;
		default: CR_Prompt(_T("huffman_tree : raised unknown exception"));
		}
	}
	catch (exception<sparse_decoder<direct_pusher>>& e)
	{
		switch (e.code)
		{
		case sparse_decoder<direct_pusher>::parse_wrong_rep:
		default: CR_Prompt(_T("sparse error : parse_wrong_rep"));
		}
	}

	byte* end = os.tell();
	return end;
}

#include <crfstream.h>

int _tmain(int argc, _TCHAR* argv[])
{
	uint32 doclen = MAXUINT; //only for text
	file_istream is;
	is.open(_T("g:\\test.txt"));
	{
		__int64 len = is.fbsize();
		if (len < MAXUINT) {
			doclen = (uint32)len;
		}
	}
	is.reset(doclen, 0);

	file_ostream os;
	os.open(_T("g:\\test.she"));
	os.putuint(doclen);
	{
		code_table_wrap<256> codes;
		return_val ret(0,0);

		try {
			ret = precompress(is, os, codes);
		}
		catch (exception<void>&) {
			is.reset(doclen, 0);
			rawcompress(is, os, ret.usesparse);
		}
		is.reset(doclen, 0);
		huffman_encoder encoder(codes, os, ret.obi);
		ret.obi = shecompress(is, os, encoder, ret.usesparse);

	}
	is.close();
	os.close();

	os.open(_T("g:\\test1.txt"));
	is.open(_T("g:\\test.she"));
	{
		decompress(is, os);
	}

	is.close();
	os.close();
	system("pause");
	return 0;
}

